package grex.genes;

import grex.BNFException;
import grex.ErrorManager;
import grex.Prediction;
import grex.PredictionContainer;

import java.text.NumberFormat;

public class LinearRegression extends Gene implements ITerminal {

    private static final int NROFCILDREN = 0;
    private int varNr = -1;
    private double k = 0,  m = 0;//Y=kx+m
    private int size = 0;
    private Double prob = new Double(-1);
    private Double lowerProb = new Double(0.0);
    private int lowerCount = 0,  probCount = 0;

    public LinearRegression() throws BNFException {
        super(NROFCILDREN);
    }

    public synchronized void execute(Gene parent, PredictionContainer data, int mode) throws GeneException {
        this.parent = parent;
        if (varNr == -1) {
            varNr = environment.getRandomContiousColumnIndex();
        }
        if (mode == Gene.TRAIN_MODE|| mode == Gene.OPTIMIZE_MODE) {
            fit(data, varNr);
            size = data.values().size();

            prob = new Double(0);
            lowerProb = new Double(0);

            lowerCount = 0;
            probCount = 0;
        }
        
        double pred = 0;
        for (Prediction p : data.values()) {
            pred = p.getInstance()[varNr] * k + m;
            if (Double.isNaN(pred)) {
                pred = 0;
            }
            
            double max = environment.getMaxTrainTarget()*1.1;
            double min = environment.getMinTrainTarget()*0.9;
            pred = Math.min(pred, max);
            pred = Math.max(pred, min);

            p.setPrediction(pred);           
//           prob += (Math.abs(target - pred) / target) / data.values().size();

            if (mode == Gene.TRAIN_MODE || mode == Gene.OPTIMIZE_MODE) {
                double target = p.getTargetValue();
                //prob += (Math.abs(target - value) / target) / data.values().size();                    
                if (pred > target) {
                    prob += (Math.abs(target - pred) / target);
                    probCount++;
                } else {
                    lowerProb += (Math.abs(target - pred) / target);
                    lowerCount++;
                }
            }else{
                p.setLowerProb(lowerProb);
                p.setProb(prob);
            }

        }
        if (mode == Gene.TRAIN_MODE || mode == Gene.OPTIMIZE_MODE) {
            if (probCount > 0) {
                prob = prob / probCount;
            }
            if (lowerCount > 0) {
                lowerProb = lowerProb / lowerCount;
            }
        }
    }

    public void fit(PredictionContainer data, int varNr) {
        double s = 0.0, sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0, del, x = 0, y = 0;
        s = data.values().size();
        if (s > 1) {
            for (Prediction p : data.values()) {
                x = p.getInstance()[varNr];
                y = p.getTargetValue();
                sx += x;
                sy += y;
                sxx += x * x;
                sxy += x * y;
            }
            del = s * sxx - sx * sx;

            // Intercept
            m = (sxx * sy - sx * sxy) / del;
            // Slope
            k = (s * sxy - sx * sy) / del;
            if (Double.isNaN(m) || Double.isNaN(k)) {
                k = 0;
                m = 0;
            }
        }
    }

    public void setVarNr(int varNr) {
        this.varNr = varNr;
    }

    public int getVarNr() {
        return varNr;
    }

    public void setKandM(double k, double m, int size) {
        this.k = k;
        this.m = m;
        this.size = size;
    }

    public Object clone() {
        
        LinearRegression newLinReg;
        try {
            newLinReg = new LinearRegression();
            newLinReg.setVarNr(varNr);
            newLinReg.setOptions(ops);
            newLinReg.setEnvironment(environment);
            newLinReg.setKandM(k, m, size);
            newLinReg.setParent(parent);
            newLinReg.lowerProb=lowerProb;
            newLinReg.prob=prob;
        } catch (grex.BNFException e) {
            ErrorManager.getInstance().reportError(e);
            return null;
        }
        return newLinReg;
    }

    public String toString() {
        try {
            NumberFormat nf = NumberFormat.getInstance();
            nf.setMaximumFractionDigits(2);
            if (varNr == -1) {
                varNr = environment.getRandomContiousColumnIndex();
            }
            return environment.getVariableNames()[varNr] + "*" + nf.format(k) + "+" + nf.format(m);
        } catch (GeneException ex) {
            ex.printStackTrace();
        }
        return "error in LinearRegression node";
    }

    public double getProb() {
        return prob;
    }

    public int getCount() {
        return size;
    }

    public double getLowProb() {
        return lowerProb;
    }
}
